#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <netinet/ip.h> // for struct ip

#define UT_ASSERT(_input, _target) do { \
    if ((_input) != (_target)) { \
        printf("assert: %s %x != %x\n", #_input, (_input), (_target)); exit(1); \
    } \
} while (0)

#define BUF_SIZE 1500

typedef unsigned char  u8;
typedef unsigned short u16;
typedef unsigned int   u32;



// struct iphdr, linux
struct ip_hdr {
    // little endian
    u8 ihl:4;
    u8 version:4;

    u8 tos;
    u16 total_len;
    
    u16 id;
    u16 offset;

    u8 ttl;
    u8 proto;
    u16 chsum;

    u32 src_addr;
    u32 dst_addr;
};



/*
unit test
0x45, 0x00, 0x00, 0x54,
0x71, 0x94, 0x40, 0x00,
0x40, 0x01, 0x42, 0x98,
0xc0, 0xa8, 0x02, 0x64,
0xc0, 0xa8, 0x02, 0xc8,

ori: ver 4, ihl 5, tos 0, tot_len 0x5400 | id 0x9471, offset 0x40 | ttl 64, proto 1, sum 0x9842 src 0x6402a8c0 dst 0xc802a8c0
lux: ver 4, ihl 5, tos 0, tot_len 0x5400 | id 0x9471, offset 0x40 | ttl 64, proto 1, sum 0x9842 src 0x6402a8c0 dst 0xc802a8c0
*/

int ut_ip_hdr(void) {
    u8 ip_hdr_buf[] = {
        0x45, 0x00, 0x00, 0x54,
        0x71, 0x94, 0x40, 0x00,
        0x40, 0x01, 0x42, 0x98,
        0xc0, 0xa8, 0x02, 0x64,
        0xc0, 0xa8, 0x02, 0xc8,
    };

    struct ip_hdr *iphdr = (void *)ip_hdr_buf;
    UT_ASSERT(iphdr->version, 5);
    UT_ASSERT(iphdr->ihl, 5);
    UT_ASSERT(iphdr->tos, 0);
    UT_ASSERT(iphdr->total_len, 0x5400);
    UT_ASSERT(iphdr->id, 0x9471);
    UT_ASSERT(iphdr->offset, 0x40);
    
    UT_ASSERT(iphdr->ttl, 64);
    UT_ASSERT(iphdr->proto, 1);
    UT_ASSERT(iphdr->chsum, 0x9842);

    UT_ASSERT(iphdr->src_addr, 0x6402a8c0);
    UT_ASSERT(iphdr->dst_addr, 0xc802a8c0);
}




int tun_alloc(char *dev) {
    struct ifreq ifr;
    int fd, err;

    if ((fd = open("/dev/net/tun", O_RDWR)) < 0) {
        perror("Opening /dev/net/tun");
        return fd;
    }

    memset(&ifr, 0, sizeof(ifr));
    ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
    if (*dev) {
        strncpy(ifr.ifr_name, dev, IFNAMSIZ);
    }

    if ((err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0) {
        perror("ioctl(TUNSETIFF)");
        close(fd);
        return err;
    }

    strcpy(dev, ifr.ifr_name);
    return fd;
}

int main() {
    char tun_name[IFNAMSIZ];
    u8 buffer[BUF_SIZE];
    int tun_fd, nread;

    strcpy(tun_name, "tun0");  // TUN 网卡名称

    if ((tun_fd = tun_alloc(tun_name)) < 0) {
        fprintf(stderr, "Error creating TUN device\n");
        exit(1);
    }

    printf("TUN device created: %s\n", tun_name);
    // /usr/include/netinet/in.h
    struct ip *ip_header = (struct ip *)(buffer);
    struct ip_hdr *iphdr = (struct ip_hdr*)buffer;

    ut_ip_hdr();

    // 监听 TUN 网卡，接收数据包
    while (1) {
        nread = read(tun_fd, buffer, sizeof(buffer));
        if (nread < 0) {
            perror("Reading from interface");
            close(tun_fd);
            exit(1);
        }
        printf("Received %d bytes from TUN device\n", nread);

        for (int i = 0; i < 20; i++) {
            if (i%4 == 0) {
                printf("\n");
            }
            printf("0x%02x, ", buffer[i]);
        }
        printf("\n");

        // 在这里可以对接收到的数据包进行处理，将数据存储到数组中或执行其他操作
        // 例如，将数据包存储到数组中
        // unsigned char packet[nread];
        // memcpy(packet, buffer, nread);
        // printf("%04x\n", buffer[0] & 0x0F);
        // printf("%08x\n", (unsigned int*)buffer);
        printf("ori: ver %u, ihl %u, tos %u, tot_len 0x%02x | id 0x%02x, offset 0x%02x | ttl %u, proto %u, sum 0x%02x src 0x%04x dst 0x%04x\n", 
            ip_header->ip_v, ip_header->ip_hl, ip_header->ip_tos, ip_header->ip_len, \
            ip_header->ip_id, ip_header->ip_off, ip_header->ip_ttl, ip_header->ip_p, ip_header->ip_sum, \
            ip_header->ip_src, ip_header->ip_dst);

        printf("lux: ver %u, ihl %u, tos %u, tot_len 0x%02x | id 0x%02x, offset 0x%02x | ttl %u, proto %u, sum 0x%02x src 0x%04x dst 0x%04x\n",
            iphdr->version, iphdr->ihl, iphdr->tos, iphdr->total_len, \
            iphdr->id, iphdr->offset, iphdr->ttl, iphdr->proto, iphdr->chsum, \
            iphdr->src_addr, iphdr->dst_addr);

    }

    close(tun_fd);
    return 0;
}

